Predicting Data Science Salaries

Anh Nguyen, Amira Bendjama, Hong Doan

1. Introduction and Problem Statement

The field of data science has experienced remarkable growth in recent years, with organizations across diverse industries recognizing the value of data-driven decision making. According to an article by 365 Data Science, the US Bureau of Labor Statistics estimated that the employment rate for data scientists will grow by 36% from 2021 to 2031. This rate is significantly higher than the average growth rate of 5%, indicating substantial growth and demand for data science talent. The surging demand for data science presents both opportunities and challenges for job seekers, particularly recent graduates. One of the significant hurdles they face is the lack of salary transparency in the data science job market. This opacity creates uncertainty regarding compensation and hinders job seekers' ability to negotiate fair salaries.

There are significant variations in data science salaries across different industries and locations. For instance, according to Zippia, data scientists working in the finance and technology sectors tend to earn higher salaries compared to those in other industries. Similarly, the geographical location also plays a crucial role in determining salaries. Large cities with higher concentration of tech companies and living costs such as San Francisco and New York offer higher salaries than smaller cities.

The discrepancies in data science salaries can also be attributed to various factors, including job responsibilities, experience level, educational background, and specific skill sets. A study conducted by Burtch Works, a leading executive recruiting firm, found that data scientists with advanced degrees, such as Ph.D., tend to command higher salaries compared to those with bachelor's or master's degrees. Similarly, professionals with expertise in specialized areas, such as machine learning or natural language processing, often earn higher salaries due to the high demand for these skills.

According to a report surveyed 1,000 US-based full-time employees, conducted by Visier, 79% of all survey respondents want some form of pay transparency and 32% want total transparency, in which all employee salaries are publicized. However, the 2022 Pay Clarity Survey by WTW found that only 17% of companies are disclosing pay range information in U.S. locations where not required by state or local laws. For the states that have pay transparency laws such as Colorado and New York, there has been a decline in job postings since the law went into effect. Some employers comply with the new laws by expanding the salary ranges, sometimes to ridiculous lengths. These statistics highlight the lack of pay transparency not only in the field of data science, but across multiple job markets. Job seekers often struggle to estimate salaries for data science positions due to the scarcity of reliable information.

To address this problem, our project aims to develop a multiclass classification model that predict the the salary range for data science jobs. By leveraging publicly available data and employing machine learning algorithms, we seek to provide job seekers a better understanding of salary expectations within the data science job market and empower them to negotiate fair and competitive compensation packages.

2. Data Sources and Data preparation

  • Install packages
#install.packages("rpart.plot")
#install.packages("ggplot2")
#install.packages("e1071")
# Install the plotly package
#install.packages("plotly")
  • Import data
# Read the first CSV file
data1 <- read.csv("ds_salaries_2023.csv")

# Read the second CSV file excluding the first column
data2 <- read.csv("ds_salaries.csv")[,-1]

# Append rows from data2 to data1
combined_data <- rbind(data2, data1)

# Write the combined data to a new CSV file
write.csv(combined_data, "combined_salaries.csv", row.names = FALSE)
library(ggplot2)
ds_salaries <- read.csv("combined_salaries.csv")
  • Data description
summary(ds_salaries)
   work_year    experience_level   employment_type     job_title        
 Min.   :2020   Length:4362        Length:4362        Length:4362       
 1st Qu.:2022   Class :character   Class :character   Class :character  
 Median :2022   Mode  :character   Mode  :character   Mode  :character  
 Mean   :2022                                                           
 3rd Qu.:2023                                                           
 Max.   :2023                                                           
     salary         salary_currency    salary_in_usd    employee_residence
 Min.   :    4000   Length:4362        Min.   :  2859   Length:4362       
 1st Qu.:   93918   Class :character   1st Qu.: 90000   Class :character  
 Median :  135000   Mode  :character   Median :130000   Mode  :character  
 Mean   :  209246                      Mean   :134054                     
 3rd Qu.:  180000                      3rd Qu.:173000                     
 Max.   :30400000                      Max.   :600000                     
  remote_ratio   company_location   company_size      
 Min.   :  0.0   Length:4362        Length:4362       
 1st Qu.:  0.0   Class :character   Class :character  
 Median : 50.0   Mode  :character   Mode  :character  
 Mean   : 49.7                                        
 3rd Qu.:100.0                                        
 Max.   :100.0                                        
  • The first 5 rows
head(ds_salaries,5)

This data set has 4362 rows and 12 columns

We want to focus on “USD” currency so we keep the “salary_in_usd” column and drop “salary_currency” and “salary” column by using subset()

ds_salaries <- subset(ds_salaries, select = -c( salary_currency, salary))
head(ds_salaries, 5)
  • Check for null values
num_null_rows <- sum(rowSums(is.na(ds_salaries)) == ncol(ds_salaries))
print(num_null_rows)
[1] 0

There are no null values

  • Check for duplicate rows
repeated_entries <- subset(ds_salaries, duplicated(ds_salaries))
print(repeated_entries)
  • Remove duplicates
# Remove duplicate rows
df <- ds_salaries[!duplicated(ds_salaries), ]
# check again
repeated_entries_new <- subset(df, duplicated(df))
print(repeated_entries_new)

Salaries groups

Adding new column to split our salaries into three groups Low , High, Medium.The approach is to use Percentiles by Dividing the dataset based on them. Hence, we are classifying salaries below the 25th percentile as “Low”, salaries between the 25th and 75th percentile as “Medium”, and salaries above the 75th percentile as “High”.

# adding new column 
# Calculate the percentiles
percentiles <- quantile(df$salary_in_usd, probs = c(0.25, 0.75))

# Define the thresholds
low_threshold <- percentiles[1]  # 25th percentile
high_threshold <- percentiles[2]  # 75th percentile

# Create a new column based on percentiles
df$salary_classification <- ifelse(df$salary_in_usd < low_threshold, "Low",
                                   ifelse(df$salary_in_usd > high_threshold, "High", "Medium"))

table(df$salary_classification)

  High    Low Medium 
   644    667   1357 
  1. Data Exploration and Visualization

Top 10 Jobs in the dataset:

# Get top 10 job titles and their value counts
top10_job_title <- head(sort(table(df$job_title), decreasing = TRUE), 10)

top10_job_title_df <- data.frame(job_title = names(top10_job_title), count = as.numeric(top10_job_title))
top10_job_title_df
NA
# Load the required packages
library(plotly)

# Define custom color palette
custom_colors <- c("#FF6361", "#FFA600", "#FFD700", "#FF76BC", "#69D2E7", "#6A0572", "#FF34B3", "#118AB2", "#FFFF99", "#FFC1CC")

# Create bar plot
fig <- plot_ly(data = top10_job_title_df, x = ~reorder(job_title, -count), y = ~count, type = "bar",
               marker = list(color = custom_colors), text = ~count) %>%
  layout(title = "Top 10 Job Titles", xaxis = list(title = "Job Titles"), yaxis = list(title = "Count"),
         font = list(size = 17), template = "plotly_dark")

# Adjust layout settings to avoid label overlap
fig <- fig %>% layout(
  margin = list(b = 150),  # Increase bottom margin to provide space for labels
  xaxis = list(
    tickangle = 45,  # Rotate x-axis tick labels
    automargin = TRUE  # Automatically adjust margins to avoid overlap
  )
)

# Display the plot
fig
NA
NA
  • The job title category distribution is imbalanced with almost 74% of the data are under the top 3 job titles (data engineer, data scientist and data analyst).

Experience level categories:

Our Dataset has 4 different experience categories: - EN: Entry-level / Junior - MI: Mid-level / Intermediate - SE: Senior-level / Expert - EX: Executive-level / Director

# Create a mapping of category abbreviations to full names
category_names_experience <- c("EN" = "Entry-level",
                    "MI" = "Mid-level",
                    "SE" = "Senior-level",
                    "EX" = "Executive-level")

# Get the sorted experience data
experience <- head(sort(table(df$experience_level), decreasing = TRUE))

# Replace the category names with full forms
names(experience) <- category_names_experience[names(experience)]

# Calculate the percentage for each category
percentages <- round(100 * experience / sum(experience), 2)

# Define a custom color palette
custom_colors <- c("#FFA998", "#FF76BC", "#69D2E7", "#FFA600")

# Create a pie chart with cute appearance
pie(experience, labels = paste(names(experience), "(", percentages, "%)"), col = custom_colors, border = "white", clockwise = TRUE, init.angle = 90)

# Add a legend with cute colors
legend("topright", legend = names(experience), fill = custom_colors, border = "white", cex = 0.8)

# Add a title with a cute font
title("Experience Distribution", font.main = 1)

  • Senior-level category accounts for almost 59% of our data, followed by mid-level (27%) and entry-level (10%). The distribution of experience category is imbalanced.

Compnay size distribution

# Create a mapping of category abbreviations to full names
category_names_company <- c("M" = "Medium",
                    "L" = "Large",
                    "S" = "Small"
                   )


# Get the sorted company size data
company_size <- head(sort(table(df$company_size), decreasing = TRUE))

# Replace the category names with full forms
names(company_size) <- category_names_company[names(company_size)]

# Set the maximum value for the y-axis
max_count <- max(company_size)

# Create a bar plot with adjusted y-axis limits
barplot(company_size, col = custom_colors, main = "Company Size Distribution", xlab = "Company Size", ylab = "Count", ylim = c(0, max_count + 10))

NA
NA
  • The company size category distribution is imbalanced with majority of the data falls under medium size.

Salaries Distribution

# Set the scipen option to a high value
options(scipen = 10)

# Create boxplot of salaries
bp <- boxplot(df$salary_in_usd / 1000, 
        col = "skyblue", 
        main = "Boxplot of Salaries",
        ylab = "Salary in Thousands USD",
        notch = TRUE)

  • For the salary attribute, the median value is a little above $100,000. The min value is around $70,000. The max value is around $300,000. There are some outlines, which show salary greater than the max value. These could be the salary of the executives.

Salaries classification Distribution



# Get the sorted salary classification data
salary_classification <- sort(table(df$salary_classification), decreasing = TRUE)


salary_classification_df <- data.frame(salary_classification= names(salary_classification ), count = as.numeric(salary_classification ))

fig <- plot_ly(
  data = salary_classification_df,
  x = ~reorder(salary_classification, -count),
  y = ~count,
  type = "bar",
  marker = list(color = custom_colors),
  text = ~count,
  width = 700,
  height = 400
)

fig <- fig %>% layout(
  title = "Salary Classification Distribution",
  xaxis = list(title = "Salary Classification"),
  yaxis = list(title = "Count"),
  font = list(size = 17),
  template = "ggplot2"
)

fig
NA
NA
NA
  • For salary classification category, the medium range accounts for approximately half of our data.
# Create a data frame with counts of experience levels by salary classification
experience_salary <- table(df$experience_level, df$salary_classification)

# Define custom colors for each experience level
custom_colors <- c("#69D2E7", "#1900ff", "#FF6361", "#FFD700")

# Create a data frame for the plot
plot_data <- data.frame(Experience = rownames(experience_salary), 
                        Salary_Classification = colnames(experience_salary), 
                        Count = as.vector(experience_salary))

# Convert Count column to numeric
plot_data$Count <- as.numeric(plot_data$Count)

# Create the bar plot
library(plotly)
fig <- plot_ly(data = plot_data, x = ~Salary_Classification, y = ~Count, 
               color = ~Experience, colors = custom_colors, type = "bar") %>%
  layout(title = "Experience Level by Salary Classification",
         xaxis = list(title = "Salary Classification"),
         yaxis = list(title = "Count"),
         font = list(size = 17),
         template = "plotly_dark")

fig
NA
NA
NA
  • Looking at the distribution of the data by salary classification and experience level, the senior level accounts for majority of data in the medium and high salary classification, which makes sense. In the low salary classfication, entry level and medium level account for the majority of data.

4. Modeling

4.1. Feature engineering

In the feature engineering process, several modifications have been made to enhance the balance and categorization of specific columns. The following changes have been implemented:

  1. Company Location and Employee Residence: The “company_location” and “employee_residence” variables have been updated to ensure better balance in the categories. The values in these columns have been transformed into either “US” or “Other”. This modification aims to create a more balanced representation of company and employee locations, which can improve the model’s performance.

  2. Job Titles: The original dataset contains a wide range of job titles (95 categories), which can lead to complexity and overfitting in the model. To simplify and generalize the job titles, they have been grouped into four categories: “Data Analyst”, “Data Engineer”, “Data Scientist”, and “Other”. This categorization allows for a more concise representation of job roles, reducing the dimensionality and enhancing interpretability in the model.

To handle the categorical columns, the “Factor()” function has been used. This function converts the categorical variables into factors, which are a type of data structure in R that represent categorical data. By converting the features into factors, it enables the model to understand and utilize the categorical information effectively.

The selected features for the model include “work_year”, “experience_level”, “employment_type”, “job_title”, “employee_residence”, “remote_ratio”, “company_location”, and “company_size”. These features provide relevant information related to work experience, level, type of employment, job title, employee and company locations, remote work ratio, and company size. The target variable for the model is the “salary_classification” column, which classifies salaries into three categories: “Low”, “Medium”, and “High”.

unique_job_titles <- unique(df$job_title)
num_unique_job_titles <- length(unique_job_titles)
num_unique_job_titles
[1] 95
df$company_location <- ifelse(df$company_location == "US", "US", "Other")
df$employee_residence <- ifelse(df$employee_residence == "US", "US", "Other")
df$job_title <- ifelse(grepl("Data Science", df$job_title) | grepl("Data Scientist", df$job_title), "Data Scientist",
                       ifelse(grepl("Analyst", df$job_title) | grepl("Analytics", df$job_title), "Data Analyst",
                                     ifelse(grepl("Data Engineer", df$job_title) | grepl("Data Engineering", df$job_title), "Data Engineer",
                                            "Other")))
table(df$job_title)

  Data Analyst  Data Engineer Data Scientist          Other 
           598            659            697            714 
table(df$employee_residence)

Other    US 
  768  1900 
table(df$company_location)

Other    US 
  732  1936 
df <- data.frame(lapply(df, factor))
factors <- sapply(df, is.factor)
factor_cols <- names(df[factors])
factor_cols
 [1] "work_year"             "experience_level"      "employment_type"      
 [4] "job_title"             "salary_in_usd"         "employee_residence"   
 [7] "remote_ratio"          "company_location"      "company_size"         
[10] "salary_classification"

a. Logistic regression

column_names <- colnames(data1)
print(column_names)
 [1] "work_year"          "experience_level"   "employment_type"   
 [4] "job_title"          "salary"             "salary_currency"   
 [7] "salary_in_usd"      "employee_residence" "remote_ratio"      
[10] "company_location"   "company_size"      


# 3 - 58
set.seed(3)  # Set a seed for reproducibility
train_indices <- sample(1:nrow(df), 0.9 * nrow(df))  # 80% for training
train_data <- df[train_indices, ]
test_data <- df[-train_indices, ]

# Separate the features (independent variables) from the target variable
X <- train_data[, !(names(train_data) %in% c("salary_in_usd", "salary_classification"))]
#X <- train_data[,c("experience_level","company_size","remote_ratio")]
Y <- train_data$salary_classification
library(nnet)

# Fit the logistic regression model
logistic_model <- multinom(Y ~ ., data = X)
# weights:  60 (38 variable)
initial  value 2637.768105 
iter  10 value 1988.010841
iter  20 value 1843.567056
iter  30 value 1827.493659
iter  40 value 1826.250743
iter  50 value 1826.149391
final  value 1826.148310 
converged
# Make predictions on the test data
test_data$predicted_classification <- predict(logistic_model, newdata = test_data)

# Evaluate model performance
library(caret)
confusion_matrix <- confusionMatrix(test_data$predicted_classification, test_data$salary_classification)

print(confusion_matrix)
Confusion Matrix and Statistics

          Reference
Prediction High Low Medium
    High     16   0     15
    Low       0  47     17
    Medium   45  15    112

Overall Statistics
                                          
               Accuracy : 0.6554          
                 95% CI : (0.5951, 0.7123)
    No Information Rate : 0.5393          
    P-Value [Acc > NIR] : 0.00007773      
                                          
                  Kappa : 0.3959          
                                          
 Mcnemar's Test P-Value : NA              

Statistics by Class:

                     Class: High Class: Low Class: Medium
Sensitivity              0.26230     0.7581        0.7778
Specificity              0.92718     0.9171        0.5122
Pos Pred Value           0.51613     0.7344        0.6512
Neg Pred Value           0.80932     0.9261        0.6632
Prevalence               0.22846     0.2322        0.5393
Detection Rate           0.05993     0.1760        0.4195
Detection Prevalence     0.11610     0.2397        0.6442
Balanced Accuracy        0.59474     0.8376        0.6450
- The accuracy of the model is reported as 0.6554, which means that approximately 65.54% of the predictions made by the model are correct. 
- The model shows relatively lower sensitivity for the "High" class and higher sensitivity for the "Low" and "Medium" classes. This indicates that the model has difficulty correctly identifying instances belonging to the "High" class, while it performs better in identifying instances from the "Low" and "Medium" classes.

b. Random Forest

# Load the randomForest package
library(randomForest)
library(caret)

# Train the Random Forest classifier
rf_model <- randomForest(X, Y)

# Make predictions on new data
# Assuming you have a data frame called test_data with similar features as train_data
predictions <- predict(rf_model, test_data)

# Calculate accuracy
accuracy <- sum(predictions == test_data$salary_classification) / length(test_data$salary_classification)
cat("Accuracy:", accuracy, "\n")
Accuracy: 0.6516854 
# Create confusion matrix
conf_matrix <- table(predictions, test_data$salary_classification)
cat("Confusion Matrix:\n")
Confusion Matrix:
print(conf_matrix)
           
predictions High Low Medium
     High      5   0      2
     Low       1  45     18
     Medium   55  17    124
# Calculate precision, recall, and F1-score for each class
class_metrics <- caret::confusionMatrix(predictions, test_data$salary_classification)
cat("Class Metrics:\n")
Class Metrics:
print(class_metrics$byClass)
              Sensitivity Specificity Pos Pred Value Neg Pred Value Precision
Class: High    0.08196721   0.9902913      0.7142857      0.7846154 0.7142857
Class: Low     0.72580645   0.9073171      0.7031250      0.9162562 0.7031250
Class: Medium  0.86111111   0.4146341      0.6326531      0.7183099 0.6326531
                  Recall        F1 Prevalence Detection Rate Detection Prevalence
Class: High   0.08196721 0.1470588  0.2284644     0.01872659           0.02621723
Class: Low    0.72580645 0.7142857  0.2322097     0.16853933           0.23970037
Class: Medium 0.86111111 0.7294118  0.5393258     0.46441948           0.73408240
              Balanced Accuracy
Class: High           0.5361292
Class: Low            0.8165618
Class: Medium         0.6378726
- The Random Forest model achieved an accuracy of approximately 0.659 (65.92%). This means that around 65.92% of the predictions made by the model on the test data were correct.
-  It demonstrates relatively higher accuracy, sensitivity, and precision for the "Low" and "Medium" classes compared to the "High" class. However, it shows lower performance in terms of sensitivity and precision for the "High" class, indicating difficulties in correctly identifying instances belonging to that class.
importance <- varImp(rf_model)
print(importance)
NA
NA

The variable importance measures obtained from the Random Forest model provide valuable insights into the relative contribution of each feature in predicting the salary classification. Among the features, “experience_level” and “employee_residence” stand out as the most influential variables with importance values of 100.35 and 108.70, respectively. These findings suggest that an employee’s experience level and their residence location play crucial roles in determining the salary classification. The “job_title” and “company_location” features also demonstrate notable importance, with values of 49.63 and 89.74, respectively, indicating that job title and company location significantly impact salary classification. Additionally, moderately important features such as “work_year” (25.56), “remote_ratio” (27.46), and “company_size” (26.49) contribute to the model’s predictions.

On the other hand, the “employment_type” feature exhibits a relatively lower importance value of 7.41, suggesting that it has a weaker impact on the model’s predictions compared to other variables. While the “employment_type” may have some relevance, it seems to provide less discriminatory power for salary classification in the context of the Random Forest model.

c. Support Vector Machine (SVM)

library(e1071)
# Train the SVM classifier
svm_model <- svm(Y ~ ., data = X, kernel = "radial")

# Make predictions on new data
# Assuming you have a data frame called test_data with similar features as train_data
predictions <- predict(svm_model, test_data)

# Evaluate the model
# Assuming you have the actual target variable values in test_data$salary_classification
accuracy <- sum(predictions == test_data$salary_classification) / length(test_data$salary_classification)
cat("Accuracy:", accuracy, "\n")
Accuracy: 0.6516854 
# Create confusion matrix
conf_matrix <- table(predictions, test_data$salary_classification)
cat("Confusion Matrix:\n")
Confusion Matrix:
print(conf_matrix)
           
predictions High Low Medium
     High      5   0      2
     Low       2  48     21
     Medium   54  14    121

d. Decision Tree

library("rpart")
library("rpart.plot")


decision_tree <- rpart(Y ~ .,
            data = X,
            method="class")

# Make predictions on test data
predictions <- predict(decision_tree, newdata = test_data, type = "class")

# Evaluate the model
accuracy <- sum(predictions == test_data$salary_classification) / nrow(test_data)
print(paste("Accuracy:", accuracy))
[1] "Accuracy: 0.640449438202247"
rpart.plot(decision_tree)

  1. Major Challenges and Solutions

    • Missing important features: such as educational background, years of experience, or industry sector
    • Not up-to-date dataset : salaries changes over time
    • Geographic Bias : mostly include data from United states
    • Dataset features are imbalance which explains the accuracy results
    • Class imbalance: it might explain the lower predictions for “High” class
    • Job titles generalization for the feature engineering
  2. Conclusion and Future Work

  3. References

    The Data Scientist Job Outlook in 2023 | 365 Data Science

    Which Industry Pays the Highest Data Scientist Salary? How To Make The Most Money As A Data Scientist - Zippia

    Burtch-Works-Study_DS-PAP-2019.pdf (burtchworks.com)

    New Visier Report Reveals 79% of Employees Want Pay Transparency (prnewswire.com)

    More NA organizations plan to disclose pay information - WTW (wtwco.com)

    Study: Pay Transparency Reduces Recruiting Costs (shrm.org)

LS0tDQp0aXRsZTogIkRhdGEgU2NpZW5jZSBTYWxhcmllcyINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCiMgUHJlZGljdGluZyBEYXRhIFNjaWVuY2UgU2FsYXJpZXMNCg0KKioqQW5oIE5ndXllbiwgQW1pcmEgQmVuZGphbWEsIEhvbmcgRG9hbioqKg0KDQojIyAxLiAqKkludHJvZHVjdGlvbiBhbmQgUHJvYmxlbSBTdGF0ZW1lbnQqKg0KDQpgYGAgICAgICAgICANClRoZSBmaWVsZCBvZiBkYXRhIHNjaWVuY2UgaGFzIGV4cGVyaWVuY2VkIHJlbWFya2FibGUgZ3Jvd3RoIGluIHJlY2VudCB5ZWFycywgd2l0aCBvcmdhbml6YXRpb25zIGFjcm9zcyBkaXZlcnNlIGluZHVzdHJpZXMgcmVjb2duaXppbmcgdGhlIHZhbHVlIG9mIGRhdGEtZHJpdmVuIGRlY2lzaW9uIG1ha2luZy4gQWNjb3JkaW5nIHRvIGFuIGFydGljbGUgYnkgMzY1IERhdGEgU2NpZW5jZSwgdGhlIFVTIEJ1cmVhdSBvZiBMYWJvciBTdGF0aXN0aWNzIGVzdGltYXRlZCB0aGF0IHRoZSBlbXBsb3ltZW50IHJhdGUgZm9yIGRhdGEgc2NpZW50aXN0cyB3aWxsIGdyb3cgYnkgMzYlIGZyb20gMjAyMSB0byAyMDMxLiBUaGlzIHJhdGUgaXMgc2lnbmlmaWNhbnRseSBoaWdoZXIgdGhhbiB0aGUgYXZlcmFnZSBncm93dGggcmF0ZSBvZiA1JSwgaW5kaWNhdGluZyBzdWJzdGFudGlhbCBncm93dGggYW5kIGRlbWFuZCBmb3IgZGF0YSBzY2llbmNlIHRhbGVudC4gVGhlIHN1cmdpbmcgZGVtYW5kIGZvciBkYXRhIHNjaWVuY2UgcHJlc2VudHMgYm90aCBvcHBvcnR1bml0aWVzIGFuZCBjaGFsbGVuZ2VzIGZvciBqb2Igc2Vla2VycywgcGFydGljdWxhcmx5IHJlY2VudCBncmFkdWF0ZXMuIE9uZSBvZiB0aGUgc2lnbmlmaWNhbnQgaHVyZGxlcyB0aGV5IGZhY2UgaXMgdGhlIGxhY2sgb2Ygc2FsYXJ5IHRyYW5zcGFyZW5jeSBpbiB0aGUgZGF0YSBzY2llbmNlIGpvYiBtYXJrZXQuIFRoaXMgb3BhY2l0eSBjcmVhdGVzIHVuY2VydGFpbnR5IHJlZ2FyZGluZyBjb21wZW5zYXRpb24gYW5kIGhpbmRlcnMgam9iIHNlZWtlcnMnIGFiaWxpdHkgdG8gbmVnb3RpYXRlIGZhaXIgc2FsYXJpZXMuDQoNClRoZXJlIGFyZSBzaWduaWZpY2FudCB2YXJpYXRpb25zIGluIGRhdGEgc2NpZW5jZSBzYWxhcmllcyBhY3Jvc3MgZGlmZmVyZW50IGluZHVzdHJpZXMgYW5kIGxvY2F0aW9ucy4gRm9yIGluc3RhbmNlLCBhY2NvcmRpbmcgdG8gWmlwcGlhLCBkYXRhIHNjaWVudGlzdHMgd29ya2luZyBpbiB0aGUgZmluYW5jZSBhbmQgdGVjaG5vbG9neSBzZWN0b3JzIHRlbmQgdG8gZWFybiBoaWdoZXIgc2FsYXJpZXMgY29tcGFyZWQgdG8gdGhvc2UgaW4gb3RoZXIgaW5kdXN0cmllcy4gU2ltaWxhcmx5LCB0aGUgZ2VvZ3JhcGhpY2FsIGxvY2F0aW9uIGFsc28gcGxheXMgYSBjcnVjaWFsIHJvbGUgaW4gZGV0ZXJtaW5pbmcgc2FsYXJpZXMuIExhcmdlIGNpdGllcyB3aXRoIGhpZ2hlciBjb25jZW50cmF0aW9uIG9mIHRlY2ggY29tcGFuaWVzIGFuZCBsaXZpbmcgY29zdHMgc3VjaCBhcyBTYW4gRnJhbmNpc2NvIGFuZCBOZXcgWW9yayBvZmZlciBoaWdoZXIgc2FsYXJpZXMgdGhhbiBzbWFsbGVyIGNpdGllcy4NCg0KVGhlIGRpc2NyZXBhbmNpZXMgaW4gZGF0YSBzY2llbmNlIHNhbGFyaWVzIGNhbiBhbHNvIGJlIGF0dHJpYnV0ZWQgdG8gdmFyaW91cyBmYWN0b3JzLCBpbmNsdWRpbmcgam9iIHJlc3BvbnNpYmlsaXRpZXMsIGV4cGVyaWVuY2UgbGV2ZWwsIGVkdWNhdGlvbmFsIGJhY2tncm91bmQsIGFuZCBzcGVjaWZpYyBza2lsbCBzZXRzLiBBIHN0dWR5IGNvbmR1Y3RlZCBieSBCdXJ0Y2ggV29ya3MsIGEgbGVhZGluZyBleGVjdXRpdmUgcmVjcnVpdGluZyBmaXJtLCBmb3VuZCB0aGF0IGRhdGEgc2NpZW50aXN0cyB3aXRoIGFkdmFuY2VkIGRlZ3JlZXMsIHN1Y2ggYXMgUGguRC4sIHRlbmQgdG8gY29tbWFuZCBoaWdoZXIgc2FsYXJpZXMgY29tcGFyZWQgdG8gdGhvc2Ugd2l0aCBiYWNoZWxvcidzIG9yIG1hc3RlcidzIGRlZ3JlZXMuIFNpbWlsYXJseSwgcHJvZmVzc2lvbmFscyB3aXRoIGV4cGVydGlzZSBpbiBzcGVjaWFsaXplZCBhcmVhcywgc3VjaCBhcyBtYWNoaW5lIGxlYXJuaW5nIG9yIG5hdHVyYWwgbGFuZ3VhZ2UgcHJvY2Vzc2luZywgb2Z0ZW4gZWFybiBoaWdoZXIgc2FsYXJpZXMgZHVlIHRvIHRoZSBoaWdoIGRlbWFuZCBmb3IgdGhlc2Ugc2tpbGxzLg0KDQpBY2NvcmRpbmcgdG8gYSByZXBvcnQgc3VydmV5ZWQgMSwwMDAgVVMtYmFzZWQgZnVsbC10aW1lIGVtcGxveWVlcywgY29uZHVjdGVkIGJ5IFZpc2llciwgNzklIG9mIGFsbCBzdXJ2ZXkgcmVzcG9uZGVudHMgd2FudCBzb21lIGZvcm0gb2YgcGF5IHRyYW5zcGFyZW5jeSBhbmQgMzIlIHdhbnQgdG90YWwgdHJhbnNwYXJlbmN5LCBpbiB3aGljaCBhbGwgZW1wbG95ZWUgc2FsYXJpZXMgYXJlIHB1YmxpY2l6ZWQuIEhvd2V2ZXIsIHRoZSAyMDIyIFBheSBDbGFyaXR5IFN1cnZleSBieSBXVFcgZm91bmQgdGhhdCBvbmx5IDE3JSBvZiBjb21wYW5pZXMgYXJlIGRpc2Nsb3NpbmcgcGF5IHJhbmdlIGluZm9ybWF0aW9uIGluIFUuUy4gbG9jYXRpb25zIHdoZXJlIG5vdCByZXF1aXJlZCBieSBzdGF0ZSBvciBsb2NhbCBsYXdzLiBGb3IgdGhlIHN0YXRlcyB0aGF0IGhhdmUgcGF5IHRyYW5zcGFyZW5jeSBsYXdzIHN1Y2ggYXMgQ29sb3JhZG8gYW5kIE5ldyBZb3JrLCB0aGVyZSBoYXMgYmVlbiBhIGRlY2xpbmUgaW4gam9iIHBvc3RpbmdzIHNpbmNlIHRoZSBsYXcgd2VudCBpbnRvIGVmZmVjdC4gU29tZSBlbXBsb3llcnMgY29tcGx5IHdpdGggdGhlIG5ldyBsYXdzIGJ5IGV4cGFuZGluZyB0aGUgc2FsYXJ5IHJhbmdlcywgc29tZXRpbWVzIHRvIHJpZGljdWxvdXMgbGVuZ3Rocy4gVGhlc2Ugc3RhdGlzdGljcyBoaWdobGlnaHQgdGhlIGxhY2sgb2YgcGF5IHRyYW5zcGFyZW5jeSBub3Qgb25seSBpbiB0aGUgZmllbGQgb2YgZGF0YSBzY2llbmNlLCBidXQgYWNyb3NzIG11bHRpcGxlIGpvYiBtYXJrZXRzLiBKb2Igc2Vla2VycyBvZnRlbiBzdHJ1Z2dsZSB0byBlc3RpbWF0ZSBzYWxhcmllcyBmb3IgZGF0YSBzY2llbmNlIHBvc2l0aW9ucyBkdWUgdG8gdGhlIHNjYXJjaXR5IG9mIHJlbGlhYmxlIGluZm9ybWF0aW9uLg0KDQpUbyBhZGRyZXNzIHRoaXMgcHJvYmxlbSwgb3VyIHByb2plY3QgYWltcyB0byBkZXZlbG9wIGEgbXVsdGljbGFzcyBjbGFzc2lmaWNhdGlvbiBtb2RlbCB0aGF0IHByZWRpY3QgdGhlIHRoZSBzYWxhcnkgcmFuZ2UgZm9yIGRhdGEgc2NpZW5jZSBqb2JzLiBCeSBsZXZlcmFnaW5nIHB1YmxpY2x5IGF2YWlsYWJsZSBkYXRhIGFuZCBlbXBsb3lpbmcgbWFjaGluZSBsZWFybmluZyBhbGdvcml0aG1zLCB3ZSBzZWVrIHRvIHByb3ZpZGUgam9iIHNlZWtlcnMgYSBiZXR0ZXIgdW5kZXJzdGFuZGluZyBvZiBzYWxhcnkgZXhwZWN0YXRpb25zIHdpdGhpbiB0aGUgZGF0YSBzY2llbmNlIGpvYiBtYXJrZXQgYW5kIGVtcG93ZXIgdGhlbSB0byBuZWdvdGlhdGUgZmFpciBhbmQgY29tcGV0aXRpdmUgY29tcGVuc2F0aW9uIHBhY2thZ2VzLg0KYGBgDQoNCiMjIDIuICoqRGF0YSBTb3VyY2VzIGFuZCBEYXRhIHByZXBhcmF0aW9uKioNCg0KLSAgIEluc3RhbGwgcGFja2FnZXMNCg0KYGBge3J9DQojaW5zdGFsbC5wYWNrYWdlcygicnBhcnQucGxvdCIpDQojaW5zdGFsbC5wYWNrYWdlcygiZ2dwbG90MiIpDQojaW5zdGFsbC5wYWNrYWdlcygiZTEwNzEiKQ0KIyBJbnN0YWxsIHRoZSBwbG90bHkgcGFja2FnZQ0KI2luc3RhbGwucGFja2FnZXMoInBsb3RseSIpDQoNCmBgYA0KDQotICAgSW1wb3J0IGRhdGENCg0KYGBge3J9DQojIFJlYWQgdGhlIGZpcnN0IENTViBmaWxlDQpkYXRhMSA8LSByZWFkLmNzdigiZHNfc2FsYXJpZXNfMjAyMy5jc3YiKQ0KDQojIFJlYWQgdGhlIHNlY29uZCBDU1YgZmlsZSBleGNsdWRpbmcgdGhlIGZpcnN0IGNvbHVtbg0KZGF0YTIgPC0gcmVhZC5jc3YoImRzX3NhbGFyaWVzLmNzdiIpWywtMV0NCg0KIyBBcHBlbmQgcm93cyBmcm9tIGRhdGEyIHRvIGRhdGExDQpjb21iaW5lZF9kYXRhIDwtIHJiaW5kKGRhdGEyLCBkYXRhMSkNCg0KIyBXcml0ZSB0aGUgY29tYmluZWQgZGF0YSB0byBhIG5ldyBDU1YgZmlsZQ0Kd3JpdGUuY3N2KGNvbWJpbmVkX2RhdGEsICJjb21iaW5lZF9zYWxhcmllcy5jc3YiLCByb3cubmFtZXMgPSBGQUxTRSkNCg0KYGBgDQoNCmBgYHtyfQ0KbGlicmFyeShnZ3Bsb3QyKQ0KZHNfc2FsYXJpZXMgPC0gcmVhZC5jc3YoImNvbWJpbmVkX3NhbGFyaWVzLmNzdiIpDQpgYGANCg0KLSAgIERhdGEgZGVzY3JpcHRpb24NCg0KYGBge3J9DQpzdW1tYXJ5KGRzX3NhbGFyaWVzKQ0KDQpgYGANCg0KLSAgIFRoZSBmaXJzdCA1IHJvd3MNCg0KYGBge3J9DQpoZWFkKGRzX3NhbGFyaWVzLDUpDQpgYGANCg0KVGhpcyBkYXRhIHNldCBoYXMgNDM2MiByb3dzIGFuZCAxMiBjb2x1bW5zDQoNCldlIHdhbnQgdG8gZm9jdXMgb24gIlVTRCIgY3VycmVuY3kgc28gd2Uga2VlcCB0aGUgInNhbGFyeV9pbl91c2QiIGNvbHVtbiBhbmQgZHJvcCAic2FsYXJ5X2N1cnJlbmN5IiBhbmQgInNhbGFyeSIgY29sdW1uIGJ5IHVzaW5nIHN1YnNldCgpDQoNCmBgYHtyfQ0KZHNfc2FsYXJpZXMgPC0gc3Vic2V0KGRzX3NhbGFyaWVzLCBzZWxlY3QgPSAtYyggc2FsYXJ5X2N1cnJlbmN5LCBzYWxhcnkpKQ0KaGVhZChkc19zYWxhcmllcywgNSkNCmBgYA0KDQotICAgQ2hlY2sgZm9yIG51bGwgdmFsdWVzDQoNCmBgYHtyfQ0KbnVtX251bGxfcm93cyA8LSBzdW0ocm93U3Vtcyhpcy5uYShkc19zYWxhcmllcykpID09IG5jb2woZHNfc2FsYXJpZXMpKQ0KcHJpbnQobnVtX251bGxfcm93cykNCmBgYA0KDQpUaGVyZSBhcmUgbm8gbnVsbCB2YWx1ZXMNCg0KLSAgIENoZWNrIGZvciBkdXBsaWNhdGUgcm93cw0KDQpgYGB7cn0NCnJlcGVhdGVkX2VudHJpZXMgPC0gc3Vic2V0KGRzX3NhbGFyaWVzLCBkdXBsaWNhdGVkKGRzX3NhbGFyaWVzKSkNCnByaW50KHJlcGVhdGVkX2VudHJpZXMpDQpgYGANCg0KDQotICAgUmVtb3ZlIGR1cGxpY2F0ZXMNCg0KYGBge3J9DQojIFJlbW92ZSBkdXBsaWNhdGUgcm93cw0KZGYgPC0gZHNfc2FsYXJpZXNbIWR1cGxpY2F0ZWQoZHNfc2FsYXJpZXMpLCBdDQojIGNoZWNrIGFnYWluDQpyZXBlYXRlZF9lbnRyaWVzX25ldyA8LSBzdWJzZXQoZGYsIGR1cGxpY2F0ZWQoZGYpKQ0KcHJpbnQocmVwZWF0ZWRfZW50cmllc19uZXcpDQpgYGANCg0KIyMjIFNhbGFyaWVzIGdyb3Vwcw0KDQpBZGRpbmcgbmV3IGNvbHVtbiB0byBzcGxpdCBvdXIgc2FsYXJpZXMgaW50byB0aHJlZSBncm91cHMgTG93ICwgSGlnaCwgTWVkaXVtLlRoZSBhcHByb2FjaCBpcyB0byB1c2UgUGVyY2VudGlsZXMgYnkgRGl2aWRpbmcgdGhlIGRhdGFzZXQgYmFzZWQgb24gdGhlbS4gSGVuY2UsIHdlIGFyZSBjbGFzc2lmeWluZyBzYWxhcmllcyBiZWxvdyB0aGUgMjV0aCBwZXJjZW50aWxlIGFzICJMb3ciLCBzYWxhcmllcyBiZXR3ZWVuIHRoZSAyNXRoIGFuZCA3NXRoIHBlcmNlbnRpbGUgYXMgIk1lZGl1bSIsIGFuZCBzYWxhcmllcyBhYm92ZSB0aGUgNzV0aCBwZXJjZW50aWxlIGFzICJIaWdoIi4NCg0KYGBge3J9DQojIGFkZGluZyBuZXcgY29sdW1uIA0KIyBDYWxjdWxhdGUgdGhlIHBlcmNlbnRpbGVzDQpwZXJjZW50aWxlcyA8LSBxdWFudGlsZShkZiRzYWxhcnlfaW5fdXNkLCBwcm9icyA9IGMoMC4yNSwgMC43NSkpDQoNCiMgRGVmaW5lIHRoZSB0aHJlc2hvbGRzDQpsb3dfdGhyZXNob2xkIDwtIHBlcmNlbnRpbGVzWzFdICAjIDI1dGggcGVyY2VudGlsZQ0KaGlnaF90aHJlc2hvbGQgPC0gcGVyY2VudGlsZXNbMl0gICMgNzV0aCBwZXJjZW50aWxlDQoNCiMgQ3JlYXRlIGEgbmV3IGNvbHVtbiBiYXNlZCBvbiBwZXJjZW50aWxlcw0KZGYkc2FsYXJ5X2NsYXNzaWZpY2F0aW9uIDwtIGlmZWxzZShkZiRzYWxhcnlfaW5fdXNkIDwgbG93X3RocmVzaG9sZCwgIkxvdyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShkZiRzYWxhcnlfaW5fdXNkID4gaGlnaF90aHJlc2hvbGQsICJIaWdoIiwgIk1lZGl1bSIpKQ0KDQp0YWJsZShkZiRzYWxhcnlfY2xhc3NpZmljYXRpb24pDQpgYGANCg0KMy4gICoqRGF0YSBFeHBsb3JhdGlvbiBhbmQgVmlzdWFsaXphdGlvbioqDQoNCiMjIyBUb3AgMTAgSm9icyBpbiB0aGUgZGF0YXNldDoNCg0KYGBge3J9DQojIEdldCB0b3AgMTAgam9iIHRpdGxlcyBhbmQgdGhlaXIgdmFsdWUgY291bnRzDQp0b3AxMF9qb2JfdGl0bGUgPC0gaGVhZChzb3J0KHRhYmxlKGRmJGpvYl90aXRsZSksIGRlY3JlYXNpbmcgPSBUUlVFKSwgMTApDQoNCnRvcDEwX2pvYl90aXRsZV9kZiA8LSBkYXRhLmZyYW1lKGpvYl90aXRsZSA9IG5hbWVzKHRvcDEwX2pvYl90aXRsZSksIGNvdW50ID0gYXMubnVtZXJpYyh0b3AxMF9qb2JfdGl0bGUpKQ0KdG9wMTBfam9iX3RpdGxlX2RmDQoNCmBgYA0KDQpgYGB7cn0NCiMgTG9hZCB0aGUgcmVxdWlyZWQgcGFja2FnZXMNCmxpYnJhcnkocGxvdGx5KQ0KDQojIERlZmluZSBjdXN0b20gY29sb3IgcGFsZXR0ZQ0KY3VzdG9tX2NvbG9ycyA8LSBjKCIjRkY2MzYxIiwgIiNGRkE2MDAiLCAiI0ZGRDcwMCIsICIjRkY3NkJDIiwgIiM2OUQyRTciLCAiIzZBMDU3MiIsICIjRkYzNEIzIiwgIiMxMThBQjIiLCAiI0ZGRkY5OSIsICIjRkZDMUNDIikNCg0KIyBDcmVhdGUgYmFyIHBsb3QNCmZpZyA8LSBwbG90X2x5KGRhdGEgPSB0b3AxMF9qb2JfdGl0bGVfZGYsIHggPSB+cmVvcmRlcihqb2JfdGl0bGUsIC1jb3VudCksIHkgPSB+Y291bnQsIHR5cGUgPSAiYmFyIiwNCiAgICAgICAgICAgICAgIG1hcmtlciA9IGxpc3QoY29sb3IgPSBjdXN0b21fY29sb3JzKSwgdGV4dCA9IH5jb3VudCkgJT4lDQogIGxheW91dCh0aXRsZSA9ICJUb3AgMTAgSm9iIFRpdGxlcyIsIHhheGlzID0gbGlzdCh0aXRsZSA9ICJKb2IgVGl0bGVzIiksIHlheGlzID0gbGlzdCh0aXRsZSA9ICJDb3VudCIpLA0KICAgICAgICAgZm9udCA9IGxpc3Qoc2l6ZSA9IDE3KSwgdGVtcGxhdGUgPSAicGxvdGx5X2RhcmsiKQ0KDQojIEFkanVzdCBsYXlvdXQgc2V0dGluZ3MgdG8gYXZvaWQgbGFiZWwgb3ZlcmxhcA0KZmlnIDwtIGZpZyAlPiUgbGF5b3V0KA0KICBtYXJnaW4gPSBsaXN0KGIgPSAxNTApLCAgIyBJbmNyZWFzZSBib3R0b20gbWFyZ2luIHRvIHByb3ZpZGUgc3BhY2UgZm9yIGxhYmVscw0KICB4YXhpcyA9IGxpc3QoDQogICAgdGlja2FuZ2xlID0gNDUsICAjIFJvdGF0ZSB4LWF4aXMgdGljayBsYWJlbHMNCiAgICBhdXRvbWFyZ2luID0gVFJVRSAgIyBBdXRvbWF0aWNhbGx5IGFkanVzdCBtYXJnaW5zIHRvIGF2b2lkIG92ZXJsYXANCiAgKQ0KKQ0KDQojIERpc3BsYXkgdGhlIHBsb3QNCmZpZw0KDQoNCmBgYA0KLSBUaGUgam9iIHRpdGxlIGNhdGVnb3J5IGRpc3RyaWJ1dGlvbiBpcyBpbWJhbGFuY2VkIHdpdGggYWxtb3N0IDc0JSBvZiB0aGUgZGF0YSBhcmUgdW5kZXIgdGhlIHRvcCAzIGpvYiB0aXRsZXMgKGRhdGEgZW5naW5lZXIsIGRhdGEgc2NpZW50aXN0IGFuZCBkYXRhIGFuYWx5c3QpLg0KDQojIyMgRXhwZXJpZW5jZSBsZXZlbCBjYXRlZ29yaWVzOg0KDQpPdXIgRGF0YXNldCBoYXMgNCBkaWZmZXJlbnQgZXhwZXJpZW5jZSBjYXRlZ29yaWVzOiAtIEVOOiBFbnRyeS1sZXZlbCAvIEp1bmlvciAtIE1JOiBNaWQtbGV2ZWwgLyBJbnRlcm1lZGlhdGUgLSBTRTogU2VuaW9yLWxldmVsIC8gRXhwZXJ0IC0gRVg6IEV4ZWN1dGl2ZS1sZXZlbCAvIERpcmVjdG9yDQoNCmBgYHtyfQ0KIyBDcmVhdGUgYSBtYXBwaW5nIG9mIGNhdGVnb3J5IGFiYnJldmlhdGlvbnMgdG8gZnVsbCBuYW1lcw0KY2F0ZWdvcnlfbmFtZXNfZXhwZXJpZW5jZSA8LSBjKCJFTiIgPSAiRW50cnktbGV2ZWwiLA0KICAgICAgICAgICAgICAgICAgICAiTUkiID0gIk1pZC1sZXZlbCIsDQogICAgICAgICAgICAgICAgICAgICJTRSIgPSAiU2VuaW9yLWxldmVsIiwNCiAgICAgICAgICAgICAgICAgICAgIkVYIiA9ICJFeGVjdXRpdmUtbGV2ZWwiKQ0KDQojIEdldCB0aGUgc29ydGVkIGV4cGVyaWVuY2UgZGF0YQ0KZXhwZXJpZW5jZSA8LSBoZWFkKHNvcnQodGFibGUoZGYkZXhwZXJpZW5jZV9sZXZlbCksIGRlY3JlYXNpbmcgPSBUUlVFKSkNCg0KIyBSZXBsYWNlIHRoZSBjYXRlZ29yeSBuYW1lcyB3aXRoIGZ1bGwgZm9ybXMNCm5hbWVzKGV4cGVyaWVuY2UpIDwtIGNhdGVnb3J5X25hbWVzX2V4cGVyaWVuY2VbbmFtZXMoZXhwZXJpZW5jZSldDQoNCiMgQ2FsY3VsYXRlIHRoZSBwZXJjZW50YWdlIGZvciBlYWNoIGNhdGVnb3J5DQpwZXJjZW50YWdlcyA8LSByb3VuZCgxMDAgKiBleHBlcmllbmNlIC8gc3VtKGV4cGVyaWVuY2UpLCAyKQ0KDQojIERlZmluZSBhIGN1c3RvbSBjb2xvciBwYWxldHRlDQpjdXN0b21fY29sb3JzIDwtIGMoIiNGRkE5OTgiLCAiI0ZGNzZCQyIsICIjNjlEMkU3IiwgIiNGRkE2MDAiKQ0KDQojIENyZWF0ZSBhIHBpZSBjaGFydCB3aXRoIGN1dGUgYXBwZWFyYW5jZQ0KcGllKGV4cGVyaWVuY2UsIGxhYmVscyA9IHBhc3RlKG5hbWVzKGV4cGVyaWVuY2UpLCAiKCIsIHBlcmNlbnRhZ2VzLCAiJSkiKSwgY29sID0gY3VzdG9tX2NvbG9ycywgYm9yZGVyID0gIndoaXRlIiwgY2xvY2t3aXNlID0gVFJVRSwgaW5pdC5hbmdsZSA9IDkwKQ0KDQojIEFkZCBhIGxlZ2VuZCB3aXRoIGN1dGUgY29sb3JzDQpsZWdlbmQoInRvcHJpZ2h0IiwgbGVnZW5kID0gbmFtZXMoZXhwZXJpZW5jZSksIGZpbGwgPSBjdXN0b21fY29sb3JzLCBib3JkZXIgPSAid2hpdGUiLCBjZXggPSAwLjgpDQoNCiMgQWRkIGEgdGl0bGUgd2l0aCBhIGN1dGUgZm9udA0KdGl0bGUoIkV4cGVyaWVuY2UgRGlzdHJpYnV0aW9uIiwgZm9udC5tYWluID0gMSkNCg0KYGBgDQotIFNlbmlvci1sZXZlbCBjYXRlZ29yeSBhY2NvdW50cyBmb3IgYWxtb3N0IDU5JSBvZiBvdXIgZGF0YSwgZm9sbG93ZWQgYnkgbWlkLWxldmVsICgyNyUpIGFuZCBlbnRyeS1sZXZlbCAoMTAlKS4gVGhlIGRpc3RyaWJ1dGlvbiBvZiBleHBlcmllbmNlIGNhdGVnb3J5IGlzIGltYmFsYW5jZWQuDQoNCg0KIyMjIENvbXBuYXkgc2l6ZSBkaXN0cmlidXRpb24NCg0KYGBge3J9DQojIENyZWF0ZSBhIG1hcHBpbmcgb2YgY2F0ZWdvcnkgYWJicmV2aWF0aW9ucyB0byBmdWxsIG5hbWVzDQpjYXRlZ29yeV9uYW1lc19jb21wYW55IDwtIGMoIk0iID0gIk1lZGl1bSIsDQogICAgICAgICAgICAgICAgICAgICJMIiA9ICJMYXJnZSIsDQogICAgICAgICAgICAgICAgICAgICJTIiA9ICJTbWFsbCINCiAgICAgICAgICAgICAgICAgICApDQoNCg0KIyBHZXQgdGhlIHNvcnRlZCBjb21wYW55IHNpemUgZGF0YQ0KY29tcGFueV9zaXplIDwtIGhlYWQoc29ydCh0YWJsZShkZiRjb21wYW55X3NpemUpLCBkZWNyZWFzaW5nID0gVFJVRSkpDQoNCiMgUmVwbGFjZSB0aGUgY2F0ZWdvcnkgbmFtZXMgd2l0aCBmdWxsIGZvcm1zDQpuYW1lcyhjb21wYW55X3NpemUpIDwtIGNhdGVnb3J5X25hbWVzX2NvbXBhbnlbbmFtZXMoY29tcGFueV9zaXplKV0NCg0KIyBTZXQgdGhlIG1heGltdW0gdmFsdWUgZm9yIHRoZSB5LWF4aXMNCm1heF9jb3VudCA8LSBtYXgoY29tcGFueV9zaXplKQ0KDQojIENyZWF0ZSBhIGJhciBwbG90IHdpdGggYWRqdXN0ZWQgeS1heGlzIGxpbWl0cw0KYmFycGxvdChjb21wYW55X3NpemUsIGNvbCA9IGN1c3RvbV9jb2xvcnMsIG1haW4gPSAiQ29tcGFueSBTaXplIERpc3RyaWJ1dGlvbiIsIHhsYWIgPSAiQ29tcGFueSBTaXplIiwgeWxhYiA9ICJDb3VudCIsIHlsaW0gPSBjKDAsIG1heF9jb3VudCArIDEwKSkNCg0KDQpgYGANCi0gVGhlIGNvbXBhbnkgc2l6ZSBjYXRlZ29yeSBkaXN0cmlidXRpb24gaXMgaW1iYWxhbmNlZCB3aXRoIG1ham9yaXR5IG9mIHRoZSBkYXRhIGZhbGxzIHVuZGVyIG1lZGl1bSBzaXplLg0KDQojIyMgU2FsYXJpZXMgRGlzdHJpYnV0aW9uDQoNCmBgYHtyfQ0KIyBTZXQgdGhlIHNjaXBlbiBvcHRpb24gdG8gYSBoaWdoIHZhbHVlDQpvcHRpb25zKHNjaXBlbiA9IDEwKQ0KDQojIENyZWF0ZSBib3hwbG90IG9mIHNhbGFyaWVzDQpicCA8LSBib3hwbG90KGRmJHNhbGFyeV9pbl91c2QgLyAxMDAwLCANCiAgICAgICAgY29sID0gInNreWJsdWUiLCANCiAgICAgICAgbWFpbiA9ICJCb3hwbG90IG9mIFNhbGFyaWVzIiwNCiAgICAgICAgeWxhYiA9ICJTYWxhcnkgaW4gVGhvdXNhbmRzIFVTRCIsDQogICAgICAgIG5vdGNoID0gVFJVRSkNCg0KYGBgDQotIEZvciB0aGUgc2FsYXJ5IGF0dHJpYnV0ZSwgdGhlIG1lZGlhbiB2YWx1ZSBpcyBhIGxpdHRsZSBhYm92ZSAkMTAwLDAwMC4gVGhlIG1pbiB2YWx1ZSBpcyBhcm91bmQgJDcwLDAwMC4gVGhlIG1heCB2YWx1ZSBpcyBhcm91bmQgJDMwMCwwMDAuIFRoZXJlIGFyZSBzb21lIG91dGxpbmVzLCB3aGljaCBzaG93IHNhbGFyeSBncmVhdGVyIHRoYW4gdGhlIG1heCB2YWx1ZS4gVGhlc2UgY291bGQgYmUgdGhlIHNhbGFyeSBvZiB0aGUgZXhlY3V0aXZlcy4NCg0KIyMjIFNhbGFyaWVzIGNsYXNzaWZpY2F0aW9uIERpc3RyaWJ1dGlvbg0KDQpgYGB7cn0NCg0KDQojIEdldCB0aGUgc29ydGVkIHNhbGFyeSBjbGFzc2lmaWNhdGlvbiBkYXRhDQpzYWxhcnlfY2xhc3NpZmljYXRpb24gPC0gc29ydCh0YWJsZShkZiRzYWxhcnlfY2xhc3NpZmljYXRpb24pLCBkZWNyZWFzaW5nID0gVFJVRSkNCg0KDQpzYWxhcnlfY2xhc3NpZmljYXRpb25fZGYgPC0gZGF0YS5mcmFtZShzYWxhcnlfY2xhc3NpZmljYXRpb249IG5hbWVzKHNhbGFyeV9jbGFzc2lmaWNhdGlvbiApLCBjb3VudCA9IGFzLm51bWVyaWMoc2FsYXJ5X2NsYXNzaWZpY2F0aW9uICkpDQoNCmZpZyA8LSBwbG90X2x5KA0KICBkYXRhID0gc2FsYXJ5X2NsYXNzaWZpY2F0aW9uX2RmLA0KICB4ID0gfnJlb3JkZXIoc2FsYXJ5X2NsYXNzaWZpY2F0aW9uLCAtY291bnQpLA0KICB5ID0gfmNvdW50LA0KICB0eXBlID0gImJhciIsDQogIG1hcmtlciA9IGxpc3QoY29sb3IgPSBjdXN0b21fY29sb3JzKSwNCiAgdGV4dCA9IH5jb3VudCwNCiAgd2lkdGggPSA3MDAsDQogIGhlaWdodCA9IDQwMA0KKQ0KDQpmaWcgPC0gZmlnICU+JSBsYXlvdXQoDQogIHRpdGxlID0gIlNhbGFyeSBDbGFzc2lmaWNhdGlvbiBEaXN0cmlidXRpb24iLA0KICB4YXhpcyA9IGxpc3QodGl0bGUgPSAiU2FsYXJ5IENsYXNzaWZpY2F0aW9uIiksDQogIHlheGlzID0gbGlzdCh0aXRsZSA9ICJDb3VudCIpLA0KICBmb250ID0gbGlzdChzaXplID0gMTcpLA0KICB0ZW1wbGF0ZSA9ICJnZ3Bsb3QyIg0KKQ0KDQpmaWcNCg0KDQoNCmBgYA0KLSBGb3Igc2FsYXJ5IGNsYXNzaWZpY2F0aW9uIGNhdGVnb3J5LCB0aGUgbWVkaXVtIHJhbmdlIGFjY291bnRzIGZvciBhcHByb3hpbWF0ZWx5IGhhbGYgb2Ygb3VyIGRhdGEuDQoNCmBgYHtyfQ0KIyBDcmVhdGUgYSBkYXRhIGZyYW1lIHdpdGggY291bnRzIG9mIGV4cGVyaWVuY2UgbGV2ZWxzIGJ5IHNhbGFyeSBjbGFzc2lmaWNhdGlvbg0KZXhwZXJpZW5jZV9zYWxhcnkgPC0gdGFibGUoZGYkZXhwZXJpZW5jZV9sZXZlbCwgZGYkc2FsYXJ5X2NsYXNzaWZpY2F0aW9uKQ0KDQojIERlZmluZSBjdXN0b20gY29sb3JzIGZvciBlYWNoIGV4cGVyaWVuY2UgbGV2ZWwNCmN1c3RvbV9jb2xvcnMgPC0gYygiIzY5RDJFNyIsICIjMTkwMGZmIiwgIiNGRjYzNjEiLCAiI0ZGRDcwMCIpDQoNCiMgQ3JlYXRlIGEgZGF0YSBmcmFtZSBmb3IgdGhlIHBsb3QNCnBsb3RfZGF0YSA8LSBkYXRhLmZyYW1lKEV4cGVyaWVuY2UgPSByb3duYW1lcyhleHBlcmllbmNlX3NhbGFyeSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgU2FsYXJ5X0NsYXNzaWZpY2F0aW9uID0gY29sbmFtZXMoZXhwZXJpZW5jZV9zYWxhcnkpLCANCiAgICAgICAgICAgICAgICAgICAgICAgIENvdW50ID0gYXMudmVjdG9yKGV4cGVyaWVuY2Vfc2FsYXJ5KSkNCg0KIyBDb252ZXJ0IENvdW50IGNvbHVtbiB0byBudW1lcmljDQpwbG90X2RhdGEkQ291bnQgPC0gYXMubnVtZXJpYyhwbG90X2RhdGEkQ291bnQpDQoNCiMgQ3JlYXRlIHRoZSBiYXIgcGxvdA0KbGlicmFyeShwbG90bHkpDQpmaWcgPC0gcGxvdF9seShkYXRhID0gcGxvdF9kYXRhLCB4ID0gflNhbGFyeV9DbGFzc2lmaWNhdGlvbiwgeSA9IH5Db3VudCwgDQogICAgICAgICAgICAgICBjb2xvciA9IH5FeHBlcmllbmNlLCBjb2xvcnMgPSBjdXN0b21fY29sb3JzLCB0eXBlID0gImJhciIpICU+JQ0KICBsYXlvdXQodGl0bGUgPSAiRXhwZXJpZW5jZSBMZXZlbCBieSBTYWxhcnkgQ2xhc3NpZmljYXRpb24iLA0KICAgICAgICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gIlNhbGFyeSBDbGFzc2lmaWNhdGlvbiIpLA0KICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gIkNvdW50IiksDQogICAgICAgICBmb250ID0gbGlzdChzaXplID0gMTcpLA0KICAgICAgICAgdGVtcGxhdGUgPSAicGxvdGx5X2RhcmsiKQ0KDQpmaWcNCg0KDQpgYGANCi0gTG9va2luZyBhdCB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBkYXRhIGJ5IHNhbGFyeSBjbGFzc2lmaWNhdGlvbiBhbmQgZXhwZXJpZW5jZSBsZXZlbCwgdGhlIHNlbmlvciBsZXZlbCBhY2NvdW50cyBmb3IgbWFqb3JpdHkgb2YgZGF0YSBpbiB0aGUgbWVkaXVtIGFuZCBoaWdoIHNhbGFyeSBjbGFzc2lmaWNhdGlvbiwgd2hpY2ggbWFrZXMgc2Vuc2UuIEluIHRoZSBsb3cgc2FsYXJ5IGNsYXNzZmljYXRpb24sIGVudHJ5IGxldmVsIGFuZCBtZWRpdW0gbGV2ZWwgYWNjb3VudCBmb3IgdGhlIG1ham9yaXR5IG9mIGRhdGEuIA0KDQojIyA0LiAqKk1vZGVsaW5nKioNCiMjIyA0LjEuIEZlYXR1cmUgZW5naW5lZXJpbmcgDQoNCkluIHRoZSBmZWF0dXJlIGVuZ2luZWVyaW5nIHByb2Nlc3MsIHNldmVyYWwgbW9kaWZpY2F0aW9ucyBoYXZlIGJlZW4gbWFkZSB0byBlbmhhbmNlIHRoZSBiYWxhbmNlIGFuZCBjYXRlZ29yaXphdGlvbiBvZiBzcGVjaWZpYyBjb2x1bW5zLiBUaGUgZm9sbG93aW5nIGNoYW5nZXMgaGF2ZSBiZWVuIGltcGxlbWVudGVkOg0KDQoxLiBDb21wYW55IExvY2F0aW9uIGFuZCBFbXBsb3llZSBSZXNpZGVuY2U6IFRoZSAiY29tcGFueV9sb2NhdGlvbiIgYW5kICJlbXBsb3llZV9yZXNpZGVuY2UiIHZhcmlhYmxlcyBoYXZlIGJlZW4gdXBkYXRlZCB0byBlbnN1cmUgYmV0dGVyIGJhbGFuY2UgaW4gdGhlIGNhdGVnb3JpZXMuIFRoZSB2YWx1ZXMgaW4gdGhlc2UgY29sdW1ucyBoYXZlIGJlZW4gdHJhbnNmb3JtZWQgaW50byBlaXRoZXIgIlVTIiBvciAiT3RoZXIiLiBUaGlzIG1vZGlmaWNhdGlvbiBhaW1zIHRvIGNyZWF0ZSBhIG1vcmUgYmFsYW5jZWQgcmVwcmVzZW50YXRpb24gb2YgY29tcGFueSBhbmQgZW1wbG95ZWUgbG9jYXRpb25zLCB3aGljaCBjYW4gaW1wcm92ZSB0aGUgbW9kZWwncyBwZXJmb3JtYW5jZS4NCg0KMi4gSm9iIFRpdGxlczogVGhlIG9yaWdpbmFsIGRhdGFzZXQgY29udGFpbnMgYSB3aWRlIHJhbmdlIG9mIGpvYiB0aXRsZXMgKDk1IGNhdGVnb3JpZXMpLCB3aGljaCBjYW4gbGVhZCB0byBjb21wbGV4aXR5IGFuZCBvdmVyZml0dGluZyBpbiB0aGUgbW9kZWwuIFRvIHNpbXBsaWZ5IGFuZCBnZW5lcmFsaXplIHRoZSBqb2IgdGl0bGVzLCB0aGV5IGhhdmUgYmVlbiBncm91cGVkIGludG8gZm91ciBjYXRlZ29yaWVzOiAiRGF0YSBBbmFseXN0IiwgIkRhdGEgRW5naW5lZXIiLCAiRGF0YSBTY2llbnRpc3QiLCBhbmQgIk90aGVyIi4gVGhpcyBjYXRlZ29yaXphdGlvbiBhbGxvd3MgZm9yIGEgbW9yZSBjb25jaXNlIHJlcHJlc2VudGF0aW9uIG9mIGpvYiByb2xlcywgcmVkdWNpbmcgdGhlIGRpbWVuc2lvbmFsaXR5IGFuZCBlbmhhbmNpbmcgaW50ZXJwcmV0YWJpbGl0eSBpbiB0aGUgbW9kZWwuDQoNClRvIGhhbmRsZSB0aGUgY2F0ZWdvcmljYWwgY29sdW1ucywgdGhlICJGYWN0b3IoKSIgZnVuY3Rpb24gaGFzIGJlZW4gdXNlZC4gVGhpcyBmdW5jdGlvbiBjb252ZXJ0cyB0aGUgY2F0ZWdvcmljYWwgdmFyaWFibGVzIGludG8gZmFjdG9ycywgd2hpY2ggYXJlIGEgdHlwZSBvZiBkYXRhIHN0cnVjdHVyZSBpbiBSIHRoYXQgcmVwcmVzZW50IGNhdGVnb3JpY2FsIGRhdGEuIEJ5IGNvbnZlcnRpbmcgdGhlIGZlYXR1cmVzIGludG8gZmFjdG9ycywgaXQgZW5hYmxlcyB0aGUgbW9kZWwgdG8gdW5kZXJzdGFuZCBhbmQgdXRpbGl6ZSB0aGUgY2F0ZWdvcmljYWwgaW5mb3JtYXRpb24gZWZmZWN0aXZlbHkuDQoNClRoZSBzZWxlY3RlZCBmZWF0dXJlcyBmb3IgdGhlIG1vZGVsIGluY2x1ZGUgIndvcmtfeWVhciIsICJleHBlcmllbmNlX2xldmVsIiwgImVtcGxveW1lbnRfdHlwZSIsICJqb2JfdGl0bGUiLCAiZW1wbG95ZWVfcmVzaWRlbmNlIiwgInJlbW90ZV9yYXRpbyIsICJjb21wYW55X2xvY2F0aW9uIiwgYW5kICJjb21wYW55X3NpemUiLiBUaGVzZSBmZWF0dXJlcyBwcm92aWRlIHJlbGV2YW50IGluZm9ybWF0aW9uIHJlbGF0ZWQgdG8gd29yayBleHBlcmllbmNlLCBsZXZlbCwgdHlwZSBvZiBlbXBsb3ltZW50LCBqb2IgdGl0bGUsIGVtcGxveWVlIGFuZCBjb21wYW55IGxvY2F0aW9ucywgcmVtb3RlIHdvcmsgcmF0aW8sIGFuZCBjb21wYW55IHNpemUuIFRoZSB0YXJnZXQgdmFyaWFibGUgZm9yIHRoZSBtb2RlbCBpcyB0aGUgInNhbGFyeV9jbGFzc2lmaWNhdGlvbiIgY29sdW1uLCB3aGljaCBjbGFzc2lmaWVzIHNhbGFyaWVzIGludG8gdGhyZWUgY2F0ZWdvcmllczogIkxvdyIsICJNZWRpdW0iLCBhbmQgIkhpZ2giLg0KDQpgYGB7cn0NCnRhYmxlKGRmJGpvYl90aXRsZSkNCmBgYA0KDQpgYGB7cn0NCmRmJGNvbXBhbnlfbG9jYXRpb24gPC0gaWZlbHNlKGRmJGNvbXBhbnlfbG9jYXRpb24gPT0gIlVTIiwgIlVTIiwgIk90aGVyIikNCmRmJGVtcGxveWVlX3Jlc2lkZW5jZSA8LSBpZmVsc2UoZGYkZW1wbG95ZWVfcmVzaWRlbmNlID09ICJVUyIsICJVUyIsICJPdGhlciIpDQpkZiRqb2JfdGl0bGUgPC0gaWZlbHNlKGdyZXBsKCJEYXRhIFNjaWVuY2UiLCBkZiRqb2JfdGl0bGUpIHwgZ3JlcGwoIkRhdGEgU2NpZW50aXN0IiwgZGYkam9iX3RpdGxlKSwgIkRhdGEgU2NpZW50aXN0IiwNCiAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKGdyZXBsKCJBbmFseXN0IiwgZGYkam9iX3RpdGxlKSB8IGdyZXBsKCJBbmFseXRpY3MiLCBkZiRqb2JfdGl0bGUpLCAiRGF0YSBBbmFseXN0IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoZ3JlcGwoIkRhdGEgRW5naW5lZXIiLCBkZiRqb2JfdGl0bGUpIHwgZ3JlcGwoIkRhdGEgRW5naW5lZXJpbmciLCBkZiRqb2JfdGl0bGUpLCAiRGF0YSBFbmdpbmVlciIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJPdGhlciIpKSkNCmBgYA0KDQpgYGB7cn0NCnRhYmxlKGRmJGpvYl90aXRsZSkNCnRhYmxlKGRmJGVtcGxveWVlX3Jlc2lkZW5jZSkNCnRhYmxlKGRmJGNvbXBhbnlfbG9jYXRpb24pDQpgYGANCg0KYGBge3J9DQpkZiA8LSBkYXRhLmZyYW1lKGxhcHBseShkZiwgZmFjdG9yKSkNCmZhY3RvcnMgPC0gc2FwcGx5KGRmLCBpcy5mYWN0b3IpDQpmYWN0b3JfY29scyA8LSBuYW1lcyhkZltmYWN0b3JzXSkNCmZhY3Rvcl9jb2xzDQpgYGANCiMjIyBhLiBMb2dpc3RpYyByZWdyZXNzaW9uDQpgYGB7cn0NCmNvbHVtbl9uYW1lcyA8LSBjb2xuYW1lcyhkYXRhMSkNCnByaW50KGNvbHVtbl9uYW1lcykNCmBgYA0KDQpgYGB7cn0NCg0KDQojIDMgLSA1OA0Kc2V0LnNlZWQoMykgICMgU2V0IGEgc2VlZCBmb3IgcmVwcm9kdWNpYmlsaXR5DQp0cmFpbl9pbmRpY2VzIDwtIHNhbXBsZSgxOm5yb3coZGYpLCAwLjkgKiBucm93KGRmKSkgICMgODAlIGZvciB0cmFpbmluZw0KdHJhaW5fZGF0YSA8LSBkZlt0cmFpbl9pbmRpY2VzLCBdDQp0ZXN0X2RhdGEgPC0gZGZbLXRyYWluX2luZGljZXMsIF0NCg0KIyBTZXBhcmF0ZSB0aGUgZmVhdHVyZXMgKGluZGVwZW5kZW50IHZhcmlhYmxlcykgZnJvbSB0aGUgdGFyZ2V0IHZhcmlhYmxlDQpYIDwtIHRyYWluX2RhdGFbLCAhKG5hbWVzKHRyYWluX2RhdGEpICVpbiUgYygic2FsYXJ5X2luX3VzZCIsICJzYWxhcnlfY2xhc3NpZmljYXRpb24iKSldDQojWCA8LSB0cmFpbl9kYXRhWyxjKCJleHBlcmllbmNlX2xldmVsIiwiY29tcGFueV9zaXplIiwicmVtb3RlX3JhdGlvIildDQpZIDwtIHRyYWluX2RhdGEkc2FsYXJ5X2NsYXNzaWZpY2F0aW9uDQpgYGANCg0KYGBge3J9DQpsaWJyYXJ5KG5uZXQpDQoNCiMgRml0IHRoZSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsDQpsb2dpc3RpY19tb2RlbCA8LSBtdWx0aW5vbShZIH4gLiwgZGF0YSA9IFgpDQoNCiMgTWFrZSBwcmVkaWN0aW9ucyBvbiB0aGUgdGVzdCBkYXRhDQp0ZXN0X2RhdGEkcHJlZGljdGVkX2NsYXNzaWZpY2F0aW9uIDwtIHByZWRpY3QobG9naXN0aWNfbW9kZWwsIG5ld2RhdGEgPSB0ZXN0X2RhdGEpDQoNCiMgRXZhbHVhdGUgbW9kZWwgcGVyZm9ybWFuY2UNCmxpYnJhcnkoY2FyZXQpDQpjb25mdXNpb25fbWF0cml4IDwtIGNvbmZ1c2lvbk1hdHJpeCh0ZXN0X2RhdGEkcHJlZGljdGVkX2NsYXNzaWZpY2F0aW9uLCB0ZXN0X2RhdGEkc2FsYXJ5X2NsYXNzaWZpY2F0aW9uKQ0KDQpwcmludChjb25mdXNpb25fbWF0cml4KQ0KDQpgYGANCg0KICAgIC0gVGhlIGFjY3VyYWN5IG9mIHRoZSBtb2RlbCBpcyByZXBvcnRlZCBhcyAwLjY1NTQsIHdoaWNoIG1lYW5zIHRoYXQgYXBwcm94aW1hdGVseSA2NS41NCUgb2YgdGhlIHByZWRpY3Rpb25zIG1hZGUgYnkgdGhlIG1vZGVsIGFyZSBjb3JyZWN0LiANCiAgICAtIFRoZSBtb2RlbCBzaG93cyByZWxhdGl2ZWx5IGxvd2VyIHNlbnNpdGl2aXR5IGZvciB0aGUgIkhpZ2giIGNsYXNzIGFuZCBoaWdoZXIgc2Vuc2l0aXZpdHkgZm9yIHRoZSAiTG93IiBhbmQgIk1lZGl1bSIgY2xhc3Nlcy4gVGhpcyBpbmRpY2F0ZXMgdGhhdCB0aGUgbW9kZWwgaGFzIGRpZmZpY3VsdHkgY29ycmVjdGx5IGlkZW50aWZ5aW5nIGluc3RhbmNlcyBiZWxvbmdpbmcgdG8gdGhlICJIaWdoIiBjbGFzcywgd2hpbGUgaXQgcGVyZm9ybXMgYmV0dGVyIGluIGlkZW50aWZ5aW5nIGluc3RhbmNlcyBmcm9tIHRoZSAiTG93IiBhbmQgIk1lZGl1bSIgY2xhc3Nlcy4NCiAgICANCg0KIyMjIGIuIFJhbmRvbSBGb3Jlc3QNCg0KYGBge3J9DQojIExvYWQgdGhlIHJhbmRvbUZvcmVzdCBwYWNrYWdlDQpsaWJyYXJ5KHJhbmRvbUZvcmVzdCkNCmxpYnJhcnkoY2FyZXQpDQoNCiMgVHJhaW4gdGhlIFJhbmRvbSBGb3Jlc3QgY2xhc3NpZmllcg0KcmZfbW9kZWwgPC0gcmFuZG9tRm9yZXN0KFgsIFkpDQoNCiMgTWFrZSBwcmVkaWN0aW9ucyBvbiBuZXcgZGF0YQ0KIyBBc3N1bWluZyB5b3UgaGF2ZSBhIGRhdGEgZnJhbWUgY2FsbGVkIHRlc3RfZGF0YSB3aXRoIHNpbWlsYXIgZmVhdHVyZXMgYXMgdHJhaW5fZGF0YQ0KcHJlZGljdGlvbnMgPC0gcHJlZGljdChyZl9tb2RlbCwgdGVzdF9kYXRhKQ0KDQojIENhbGN1bGF0ZSBhY2N1cmFjeQ0KYWNjdXJhY3kgPC0gc3VtKHByZWRpY3Rpb25zID09IHRlc3RfZGF0YSRzYWxhcnlfY2xhc3NpZmljYXRpb24pIC8gbGVuZ3RoKHRlc3RfZGF0YSRzYWxhcnlfY2xhc3NpZmljYXRpb24pDQpjYXQoIkFjY3VyYWN5OiIsIGFjY3VyYWN5LCAiXG4iKQ0KDQojIENyZWF0ZSBjb25mdXNpb24gbWF0cml4DQpjb25mX21hdHJpeCA8LSB0YWJsZShwcmVkaWN0aW9ucywgdGVzdF9kYXRhJHNhbGFyeV9jbGFzc2lmaWNhdGlvbikNCmNhdCgiQ29uZnVzaW9uIE1hdHJpeDpcbiIpDQpwcmludChjb25mX21hdHJpeCkNCg0KIyBDYWxjdWxhdGUgcHJlY2lzaW9uLCByZWNhbGwsIGFuZCBGMS1zY29yZSBmb3IgZWFjaCBjbGFzcw0KY2xhc3NfbWV0cmljcyA8LSBjYXJldDo6Y29uZnVzaW9uTWF0cml4KHByZWRpY3Rpb25zLCB0ZXN0X2RhdGEkc2FsYXJ5X2NsYXNzaWZpY2F0aW9uKQ0KY2F0KCJDbGFzcyBNZXRyaWNzOlxuIikNCnByaW50KGNsYXNzX21ldHJpY3MkYnlDbGFzcykNCmBgYA0KICAgIC0gVGhlIFJhbmRvbSBGb3Jlc3QgbW9kZWwgYWNoaWV2ZWQgYW4gYWNjdXJhY3kgb2YgYXBwcm94aW1hdGVseSAwLjY1OSAoNjUuOTIlKS4gVGhpcyBtZWFucyB0aGF0IGFyb3VuZCA2NS45MiUgb2YgdGhlIHByZWRpY3Rpb25zIG1hZGUgYnkgdGhlIG1vZGVsIG9uIHRoZSB0ZXN0IGRhdGEgd2VyZSBjb3JyZWN0Lg0KICAgIC0gIEl0IGRlbW9uc3RyYXRlcyByZWxhdGl2ZWx5IGhpZ2hlciBhY2N1cmFjeSwgc2Vuc2l0aXZpdHksIGFuZCBwcmVjaXNpb24gZm9yIHRoZSAiTG93IiBhbmQgIk1lZGl1bSIgY2xhc3NlcyBjb21wYXJlZCB0byB0aGUgIkhpZ2giIGNsYXNzLiBIb3dldmVyLCBpdCBzaG93cyBsb3dlciBwZXJmb3JtYW5jZSBpbiB0ZXJtcyBvZiBzZW5zaXRpdml0eSBhbmQgcHJlY2lzaW9uIGZvciB0aGUgIkhpZ2giIGNsYXNzLCBpbmRpY2F0aW5nIGRpZmZpY3VsdGllcyBpbiBjb3JyZWN0bHkgaWRlbnRpZnlpbmcgaW5zdGFuY2VzIGJlbG9uZ2luZyB0byB0aGF0IGNsYXNzLg0KICAgIA0KYGBge3J9DQppbXBvcnRhbmNlIDwtIHZhckltcChyZl9tb2RlbCkNCnByaW50KGltcG9ydGFuY2UpDQoNCmBgYA0KDQpUaGUgdmFyaWFibGUgaW1wb3J0YW5jZSBtZWFzdXJlcyBvYnRhaW5lZCBmcm9tIHRoZSBSYW5kb20gRm9yZXN0IG1vZGVsIHByb3ZpZGUgdmFsdWFibGUgaW5zaWdodHMgaW50byB0aGUgcmVsYXRpdmUgY29udHJpYnV0aW9uIG9mIGVhY2ggZmVhdHVyZSBpbiBwcmVkaWN0aW5nIHRoZSBzYWxhcnkgY2xhc3NpZmljYXRpb24uIEFtb25nIHRoZSBmZWF0dXJlcywgImV4cGVyaWVuY2VfbGV2ZWwiIGFuZCAiZW1wbG95ZWVfcmVzaWRlbmNlIiBzdGFuZCBvdXQgYXMgdGhlIG1vc3QgaW5mbHVlbnRpYWwgdmFyaWFibGVzIHdpdGggaW1wb3J0YW5jZSB2YWx1ZXMgb2YgMTAwLjM1IGFuZCAxMDguNzAsIHJlc3BlY3RpdmVseS4gVGhlc2UgZmluZGluZ3Mgc3VnZ2VzdCB0aGF0IGFuIGVtcGxveWVlJ3MgZXhwZXJpZW5jZSBsZXZlbCBhbmQgdGhlaXIgcmVzaWRlbmNlIGxvY2F0aW9uIHBsYXkgY3J1Y2lhbCByb2xlcyBpbiBkZXRlcm1pbmluZyB0aGUgc2FsYXJ5IGNsYXNzaWZpY2F0aW9uLiBUaGUgImpvYl90aXRsZSIgYW5kICJjb21wYW55X2xvY2F0aW9uIiBmZWF0dXJlcyBhbHNvIGRlbW9uc3RyYXRlIG5vdGFibGUgaW1wb3J0YW5jZSwgd2l0aCB2YWx1ZXMgb2YgNDkuNjMgYW5kIDg5Ljc0LCByZXNwZWN0aXZlbHksIGluZGljYXRpbmcgdGhhdCBqb2IgdGl0bGUgYW5kIGNvbXBhbnkgbG9jYXRpb24gc2lnbmlmaWNhbnRseSBpbXBhY3Qgc2FsYXJ5IGNsYXNzaWZpY2F0aW9uLiBBZGRpdGlvbmFsbHksIG1vZGVyYXRlbHkgaW1wb3J0YW50IGZlYXR1cmVzIHN1Y2ggYXMgIndvcmtfeWVhciIgKDI1LjU2KSwgInJlbW90ZV9yYXRpbyIgKDI3LjQ2KSwgYW5kICJjb21wYW55X3NpemUiICgyNi40OSkgY29udHJpYnV0ZSB0byB0aGUgbW9kZWwncyBwcmVkaWN0aW9ucy4NCg0KT24gdGhlIG90aGVyIGhhbmQsIHRoZSAiZW1wbG95bWVudF90eXBlIiBmZWF0dXJlIGV4aGliaXRzIGEgcmVsYXRpdmVseSBsb3dlciBpbXBvcnRhbmNlIHZhbHVlIG9mIDcuNDEsIHN1Z2dlc3RpbmcgdGhhdCBpdCBoYXMgYSB3ZWFrZXIgaW1wYWN0IG9uIHRoZSBtb2RlbCdzIHByZWRpY3Rpb25zIGNvbXBhcmVkIHRvIG90aGVyIHZhcmlhYmxlcy4gV2hpbGUgdGhlICJlbXBsb3ltZW50X3R5cGUiIG1heSBoYXZlIHNvbWUgcmVsZXZhbmNlLCBpdCBzZWVtcyB0byBwcm92aWRlIGxlc3MgZGlzY3JpbWluYXRvcnkgcG93ZXIgZm9yIHNhbGFyeSBjbGFzc2lmaWNhdGlvbiBpbiB0aGUgY29udGV4dCBvZiB0aGUgUmFuZG9tIEZvcmVzdCBtb2RlbC4NCg0KIyMjIGMuIFN1cHBvcnQgVmVjdG9yIE1hY2hpbmUgKFNWTSkNCg0KYGBge3J9DQpsaWJyYXJ5KGUxMDcxKQ0KIyBUcmFpbiB0aGUgU1ZNIGNsYXNzaWZpZXINCnN2bV9tb2RlbCA8LSBzdm0oWSB+IC4sIGRhdGEgPSBYLCBrZXJuZWwgPSAicmFkaWFsIikNCg0KIyBNYWtlIHByZWRpY3Rpb25zIG9uIG5ldyBkYXRhDQojIEFzc3VtaW5nIHlvdSBoYXZlIGEgZGF0YSBmcmFtZSBjYWxsZWQgdGVzdF9kYXRhIHdpdGggc2ltaWxhciBmZWF0dXJlcyBhcyB0cmFpbl9kYXRhDQpwcmVkaWN0aW9ucyA8LSBwcmVkaWN0KHN2bV9tb2RlbCwgdGVzdF9kYXRhKQ0KDQojIEV2YWx1YXRlIHRoZSBtb2RlbA0KIyBBc3N1bWluZyB5b3UgaGF2ZSB0aGUgYWN0dWFsIHRhcmdldCB2YXJpYWJsZSB2YWx1ZXMgaW4gdGVzdF9kYXRhJHNhbGFyeV9jbGFzc2lmaWNhdGlvbg0KYWNjdXJhY3kgPC0gc3VtKHByZWRpY3Rpb25zID09IHRlc3RfZGF0YSRzYWxhcnlfY2xhc3NpZmljYXRpb24pIC8gbGVuZ3RoKHRlc3RfZGF0YSRzYWxhcnlfY2xhc3NpZmljYXRpb24pDQpjYXQoIkFjY3VyYWN5OiIsIGFjY3VyYWN5LCAiXG4iKQ0KDQojIENyZWF0ZSBjb25mdXNpb24gbWF0cml4DQpjb25mX21hdHJpeCA8LSB0YWJsZShwcmVkaWN0aW9ucywgdGVzdF9kYXRhJHNhbGFyeV9jbGFzc2lmaWNhdGlvbikNCmNhdCgiQ29uZnVzaW9uIE1hdHJpeDpcbiIpDQpwcmludChjb25mX21hdHJpeCkNCmBgYA0KDQojIyMgZC4gRGVjaXNpb24gVHJlZQ0KDQpgYGB7cn0NCmxpYnJhcnkoInJwYXJ0IikNCmxpYnJhcnkoInJwYXJ0LnBsb3QiKQ0KDQoNCmRlY2lzaW9uX3RyZWUgPC0gcnBhcnQoWSB+IC4sDQogICAgICAgICAgICBkYXRhID0gWCwNCiAgICAgICAgICAgIG1ldGhvZD0iY2xhc3MiKQ0KDQojIE1ha2UgcHJlZGljdGlvbnMgb24gdGVzdCBkYXRhDQpwcmVkaWN0aW9ucyA8LSBwcmVkaWN0KGRlY2lzaW9uX3RyZWUsIG5ld2RhdGEgPSB0ZXN0X2RhdGEsIHR5cGUgPSAiY2xhc3MiKQ0KDQojIEV2YWx1YXRlIHRoZSBtb2RlbA0KYWNjdXJhY3kgPC0gc3VtKHByZWRpY3Rpb25zID09IHRlc3RfZGF0YSRzYWxhcnlfY2xhc3NpZmljYXRpb24pIC8gbnJvdyh0ZXN0X2RhdGEpDQpwcmludChwYXN0ZSgiQWNjdXJhY3k6IiwgYWNjdXJhY3kpKQ0KcnBhcnQucGxvdChkZWNpc2lvbl90cmVlKQ0KDQpgYGANCg0KNi4gICoqTWFqb3IgQ2hhbGxlbmdlcyBhbmQgU29sdXRpb25zXA0KICAgICoqDQogICAgLSBNaXNzaW5nIGltcG9ydGFudCBmZWF0dXJlczogc3VjaCBhcyBlZHVjYXRpb25hbCBiYWNrZ3JvdW5kLCB5ZWFycyBvZiBleHBlcmllbmNlLCBvciBpbmR1c3RyeSBzZWN0b3IgIA0KICAgIC0gTm90IHVwLXRvLWRhdGUgZGF0YXNldCA6IHNhbGFyaWVzIGNoYW5nZXMgb3ZlciB0aW1lDQogICAgLSBHZW9ncmFwaGljIEJpYXMgOiBtb3N0bHkgaW5jbHVkZSBkYXRhIGZyb20gVW5pdGVkIHN0YXRlcyANCiAgICAtIERhdGFzZXQgZmVhdHVyZXMgYXJlIGltYmFsYW5jZSB3aGljaCBleHBsYWlucyB0aGUgYWNjdXJhY3kgcmVzdWx0cw0KICAgIC0gQ2xhc3MgaW1iYWxhbmNlOiBpdCBtaWdodCBleHBsYWluIHRoZSBsb3dlciBwcmVkaWN0aW9ucyBmb3Ig4oCcSGlnaOKAnSBjbGFzcw0KICAgIC0gSm9iIHRpdGxlcyBnZW5lcmFsaXphdGlvbiBmb3IgdGhlIGZlYXR1cmUgZW5naW5lZXJpbmcgDQoNCg0KDQo3LiAgKipDb25jbHVzaW9uIGFuZCBGdXR1cmUgV29yayoqDQoNCjguICAqKlJlZmVyZW5jZXMqKg0KDQogICAgW1RoZSBEYXRhIFNjaWVudGlzdCBKb2IgT3V0bG9vayBpbiAyMDIzIFx8IDM2NSBEYXRhIFNjaWVuY2VdKGh0dHBzOi8vMzY1ZGF0YXNjaWVuY2UuY29tL2NhcmVlci1hZHZpY2UvZGF0YS1zY2llbnRpc3Qtam9iLW91dGxvb2svKQ0KDQogICAgW1doaWNoIEluZHVzdHJ5IFBheXMgdGhlIEhpZ2hlc3QgRGF0YSBTY2llbnRpc3QgU2FsYXJ5PyBIb3cgVG8gTWFrZSBUaGUgTW9zdCBNb25leSBBcyBBIERhdGEgU2NpZW50aXN0IC0gWmlwcGlhXShodHRwczovL3d3dy56aXBwaWEuY29tL2FkdmljZS9oaWdoZXN0LXBheWluZy1kYXRhLXNjaWVudGlzdC1qb2JzLykNCg0KICAgIFtCdXJ0Y2gtV29ya3MtU3R1ZHlfRFMtUEFQLTIwMTkucGRmIChidXJ0Y2h3b3Jrcy5jb20pXShodHRwczovL3d3dy5idXJ0Y2h3b3Jrcy5jb20vd3AtY29udGVudC91cGxvYWRzLzIwMTkvMDYvQnVydGNoLVdvcmtzLVN0dWR5X0RTLVBBUC0yMDE5LnBkZikNCg0KICAgIFtOZXcgVmlzaWVyIFJlcG9ydCBSZXZlYWxzIDc5JSBvZiBFbXBsb3llZXMgV2FudCBQYXkgVHJhbnNwYXJlbmN5IChwcm5ld3N3aXJlLmNvbSldKGh0dHBzOi8vd3d3LnBybmV3c3dpcmUuY29tL25ld3MtcmVsZWFzZXMvbmV3LXZpc2llci1yZXBvcnQtcmV2ZWFscy03OS1vZi1lbXBsb3llZXMtd2FudC1wYXktdHJhbnNwYXJlbmN5LTMwMTUyNzMwNS5odG1sKQ0KDQogICAgW01vcmUgTkEgb3JnYW5pemF0aW9ucyBwbGFuIHRvIGRpc2Nsb3NlIHBheSBpbmZvcm1hdGlvbiAtIFdUVyAod3R3Y28uY29tKV0oaHR0cHM6Ly93d3cud3R3Y28uY29tL2VuLXVzL25ld3MvMjAyMi8wOS9tb3JlLW5vcnRoLWFtZXJpY2FuLW9yZ2FuaXphdGlvbnMtcGxhbi10by1kaXNjbG9zZS1wYXktaW5mb3JtYXRpb24tc3VydmV5LWZpbmRzKQ0KDQogICAgW1N0dWR5OiBQYXkgVHJhbnNwYXJlbmN5IFJlZHVjZXMgUmVjcnVpdGluZyBDb3N0cyAoc2hybS5vcmcpXShodHRwczovL3d3dy5zaHJtLm9yZy9yZXNvdXJjZXNhbmR0b29scy9oci10b3BpY3MvdGFsZW50LWFjcXVpc2l0aW9uL3BhZ2VzL3BheS10cmFuc3BhcmVuY3ktcmVkdWNlcy1yZWNydWl0aW5nLWNvc3RzLmFzcHgpDQo=